/* Copyright (c) 2007, Jan Flora <janflora@diku.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the University of Copenhagen nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
  @author Jan Flora <janflora@diku.dk>
*/

#include <macTypes.h>
#include <macConstants.h>
#include <Int64Compare.h>

module pongM
{
	provides
	{
		interface StdControl;
	}
	uses
	{
	    interface IeeeMacPibAttribute as IeeePibAttribute;
		interface IeeeMacSdu as IeeeSdu;
		interface IeeeAddress;
	   
		interface McpsDataIndication;
		interface McpsDataRequestConfirm;
	
		interface MlmeAssociateIndicationResponse;
		interface MlmeSetRequestConfirm;
		interface MlmeStartRequestConfirm;
		interface MlmeCommStatusIndication;
		interface MlmeGtsIndication;

		// MCPS
		interface IeeeIndication<Mcps_DataIndication> as McpsIndicationData;   	
		interface IeeeRequestConfirm<Mcps_DataRequestConfirm> as McpsRequestConfirmData;

		// MLME	
		interface IeeeIndicationResponse<Mlme_AssociateIndicationResponse> as MlmeIndicationResponseAssociate;
		interface IeeeSyncRequestConfirm<Mlme_SetRequestConfirm> as MlmeRequestConfirmSet;
		interface IeeeRequestConfirm<Mlme_StartRequestConfirm> as MlmeRequestConfirmStart;
		interface IeeeIndication<Mlme_CommStatusIndication> as MlmeIndicationCommStatus;
		interface IeeeIndication<Mlme_GtsIndication> as MlmeIndicationGts;

		interface Debug;
	}
}
implementation
{
	#define PROFILING
	//#define DBG_OFF 0
	#define DBG_LEVEL 1
	#include "Debug.h"
	
	#ifdef PROFILING
		// Set up profiling pins.
		TOSH_ASSIGN_PIN(PROFILING1, C, 5);
		TOSH_ASSIGN_PIN(PROFILING2, C, 6);
		TOSH_ASSIGN_PIN(PROFILING3, C, 7);
		TOSH_ASSIGN_PIN(PROFILING4, G, 3);
		TOSH_ASSIGN_PIN(PROFILING5, G, 1);
		TOSH_ASSIGN_PIN(PROFILING6, B, 7);
		TOSH_ASSIGN_PIN(PROFILING7, B, 3);
		TOSH_ASSIGN_PIN(PROFILING8, B, 2);
		TOSH_ASSIGN_PIN(PROFILING9, B, 1);
	#endif
	
	uint8_t logicalChannel = 12;
	uint16_t myPanId = 0xDEAD;
	uint16_t myShortAddr = 0x0001;
	uint8_t myMsduHandle = 0x01;
	Ieee_Address pingReplyAddr;
	
	// Forward declarations of helper functions.
	task void pingReply();
	task void startPan();
	result_t setAttribute(Ieee_PibAttribute attribute);
	void setShort(uint16_t myShort);
	void setAssoc(bool allow);
	void setRxOnWhenIdle(bool status);
	int strcmp(const char * a, const char * b);
	
	command result_t StdControl.init()
	{
		#ifdef PROFILING
			// Setup profiling pins as output pins.
			TOSH_MAKE_PROFILING1_OUTPUT();
			TOSH_MAKE_PROFILING2_OUTPUT();
			TOSH_MAKE_PROFILING3_OUTPUT();
			TOSH_MAKE_PROFILING4_OUTPUT();
			TOSH_MAKE_PROFILING5_OUTPUT();
			TOSH_MAKE_PROFILING6_OUTPUT();
			TOSH_MAKE_PROFILING7_OUTPUT();
			TOSH_MAKE_PROFILING8_OUTPUT();
			TOSH_MAKE_PROFILING9_OUTPUT();
			
			// Clear all pins.
			TOSH_CLR_PROFILING1_PIN();
			TOSH_CLR_PROFILING2_PIN();
			TOSH_CLR_PROFILING3_PIN();
			TOSH_CLR_PROFILING4_PIN();
			TOSH_CLR_PROFILING5_PIN();
			TOSH_CLR_PROFILING6_PIN();
			TOSH_CLR_PROFILING7_PIN();
			TOSH_CLR_PROFILING8_PIN();
			TOSH_CLR_PROFILING9_PIN();
		#endif
	
		call IeeeAddress.create(&pingReplyAddr);
		return SUCCESS;
	}

	command result_t StdControl.start()
	{
		uint8_t myAddr[8] = {0x61, 0x03, 0x01, 0xB0, 0x37, 0xC2, 0x50, 0x00};
		memcpy(aExtendedAddress, myAddr, 8);
		post startPan();
		return SUCCESS;
	}

	command result_t StdControl.stop()
	{
		return FAIL;
	}
	
	/*******************************
	  Data handling        
	 *******************************/
	
	event void McpsIndicationData.indication(Mcps_DataIndication indication)
	{
		Ieee_Msdu msdu;
		char *payload;
		uint8_t payloadLen;
		
		#ifdef PROFILING
			TOSH_SET_PROFILING1_PIN();
		#endif
		
		call IeeeSdu.create(&msdu);
		call McpsDataIndication.getMsdu(indication, msdu);
		payload = (char*)call IeeeSdu.getPayload(msdu);
		payloadLen = call IeeeSdu.getPayloadLen(msdu);
		
		if (payloadLen == 5 && 0 == strcmp("ping", payload)) {
			DBG_STR("Received a ping request.",1);
			call McpsDataIndication.getSrcAddress(indication, pingReplyAddr);
			post pingReply();
		} else {
			DBG_STR("Ignoring data received different from ping request.",1);
			DBG_DUMP(payload, payloadLen, 1);
		}

		call McpsDataIndication.destroy(indication);
		call IeeeSdu.destroy(msdu);
		
		#ifdef PROFILING
			TOSH_CLR_PROFILING1_PIN();
		#endif
	}

	event void McpsRequestConfirmData.confirm( Mcps_DataRequestConfirm confirm )
	{
		//uint8_t handle = call McpsDataRequestConfirm.getMsduHandle(confirm);
		Ieee_Status status = call McpsDataRequestConfirm.getStatus(confirm);
		
		#ifdef PROFILING
			TOSH_CLR_PROFILING9_PIN();
		#endif
		
		if (status == IEEE802154_SUCCESS) {
			DBG_STR("Ping reply transmitted.",1);
		} else {
			DBG_STR("Ping reply failed.",1);
		}
		call McpsDataRequestConfirm.destroy(confirm);
		
		#ifdef PROFILING
			TOSH_CLR_PROFILING2_PIN();
		#endif
	}
	
	task void pingReply()
	{
		Ieee_Address srcAddr;
		Mcps_DataRequestConfirm request;
		Ieee_Msdu myMsdu;
		char *payload = "pong";
		
		#ifdef PROFILING
			TOSH_SET_PROFILING2_PIN();
		#endif
		
		if (FAIL == call IeeeAddress.create( &srcAddr )) {
			DBG_STR("Failed to allocate SrcAddr",1);
		}
		if (FAIL == call IeeeSdu.create( &myMsdu )) {
			DBG_STR("Failed to allocate SDU",1);
		}

		// Fill out the source address.
		call IeeeAddress.setAddrMode( srcAddr, 2 );
		call IeeeAddress.setAddress( srcAddr, (uint8_t*)&myShortAddr );
		call IeeeAddress.setPanId( srcAddr, myPanId);
    
		// Create request
		call McpsDataRequestConfirm.create(&request, srcAddr, pingReplyAddr, 5);

		call McpsDataRequestConfirm.getMsdu( request, myMsdu );
		memcpy(call IeeeSdu.getPayload( myMsdu ), payload, 5);
		call McpsDataRequestConfirm.setMsduHandle( request, myMsduHandle );
		call McpsDataRequestConfirm.setTxOptions( request, (IEEE802154_TxOptsAck | IEEE802154_TxOptsIndirectTx) );

		if (SUCCESS == call McpsRequestConfirmData.request(request)) {
			DBG_STR("Ping reply was enqueued.",1);
			myMsduHandle++;
		} else {
			DBG_STR("Transmission of ping reply failed.",1);
		}
		
		// Clean up.
		call IeeeAddress.destroy( srcAddr );
		call IeeeSdu.destroy( myMsdu );
	}
	
	/*******************************
	  GTS handling
	 *******************************/
	
	event void MlmeIndicationGts.indication( Mlme_GtsIndication indication )
  	{
  		uint16_t deviceAddr;
  		uint8_t gtsChars;
  		
  		#ifdef PROFILING
  			TOSH_SET_PROFILING3_PIN();
  		#endif
  		
  		deviceAddr = call MlmeGtsIndication.getDevAddress(indication);
  		gtsChars = call MlmeGtsIndication.getGtsCharacteristics(indication);
  		
  		if (gtsChars & 0x10) {
  			if (gtsChars & 0x20) {
  				DBG_STRINT("A receive GTS was allocated for device", deviceAddr, 1);
  			} else {
  				DBG_STRINT("A receive GTS was deallocated for device", deviceAddr, 1);
  			}
  		} else {
  			if (gtsChars & 0x20) {
  				DBG_STRINT("A transmit GTS was allocated for device", deviceAddr, 1);
  			} else {
  				DBG_STRINT("A transmit GTS was deallocated for device", deviceAddr, 1);
  			}
  		}
  		
  		call MlmeGtsIndication.destroy(indication);
  		
  		#ifdef PROFILING
  			TOSH_CLR_PROFILING3_PIN();
  		#endif
  	}
	
	/*******************************
	  Association handling
	 *******************************/
	
	async event void MlmeIndicationResponseAssociate.indication( Mlme_AssociateIndicationResponse indication )
	{
		uint8_t assocDeviceAddr[8];
		
		#ifdef PROFILING
			TOSH_SET_PROFILING4_PIN();
		#endif
		
		call MlmeAssociateIndicationResponse.getDeviceAddress(indication, assocDeviceAddr);
		DBG_STR("Device with address:",1);
		DBG_DUMP(assocDeviceAddr, 8, 1);
		DBG_STR("is requesting association.",1);

		// Assign short address 0xBEEF to the device.
		call MlmeAssociateIndicationResponse.setDeviceAddress(indication,assocDeviceAddr);
		call MlmeAssociateIndicationResponse.setAssocShortAddress(indication,0xBEEF);
		call MlmeAssociateIndicationResponse.setStatus(indication,IEEE802154_SUCCESS);
		call MlmeIndicationResponseAssociate.response(indication);
		
		#ifdef PROFILING
			TOSH_CLR_PROFILING4_PIN();
		#endif
	}
	
	event void MlmeIndicationCommStatus.indication( Mlme_CommStatusIndication indication )
	{
		Ieee_Status status;
		
		#ifdef PROFILING
			TOSH_SET_PROFILING6_PIN();
		#endif
		
		// The comm status indication must be the result of an association response.
		status = call MlmeCommStatusIndication.getStatus(indication);
		
		if (status == IEEE802154_SUCCESS) {
			DBG_STR("Requesting device was granted association.",1);
		} else {
			DBG_STRINT("Transmitting associatin response failed with status: ",status,1);
		}
		
		// Clean up.
		call MlmeCommStatusIndication.destroy(indication);
		
		#ifdef PROFILING
			TOSH_CLR_PROFILING6_PIN();
		#endif
	}
	
	/*******************************
	  PAN coordinator role handling
	 *******************************/

	task void startPan()
	{
		Mlme_StartRequestConfirm request;
		
		#ifdef PROFILING
			TOSH_SET_PROFILING5_PIN();
		#endif
		
		DBG_STR("Starting up device as beaconing PAN coordinator...",1);
		
		// Set my short address and association permit first.
		setShort(myShortAddr);
		setAssoc(TRUE);
		setRxOnWhenIdle(TRUE);
		
		// Establish device as a PAN coordinator.
		call MlmeStartRequestConfirm.create(&request);
		call MlmeStartRequestConfirm.setPanId(request,myPanId);
		call MlmeStartRequestConfirm.setLogicalChannel(request,logicalChannel);
		call MlmeStartRequestConfirm.setBeaconOrder(request,0x8);
		call MlmeStartRequestConfirm.setSuperframeOrder(request,0x8);
		call MlmeStartRequestConfirm.setPanCoordinator(request,TRUE);
		call MlmeStartRequestConfirm.setBatteryLifeExtension(request,FALSE);
		call MlmeStartRequestConfirm.setCoordRealignment(request,FALSE);
		call MlmeStartRequestConfirm.setSecurityEnable(request,FALSE);
		call MlmeRequestConfirmStart.request(request);
	}
	
	event void MlmeRequestConfirmStart.confirm(Mlme_StartRequestConfirm confirm)
	{
		Ieee_Status status = call MlmeStartRequestConfirm.getStatus(confirm);
		
		#ifdef PROFILING
			TOSH_SET_PROFILING9_PIN();
		#endif
		
		if (status == IEEE802154_SUCCESS) {
			DBG_STR("Device is now acting as PAN coordinator.",1);
		} else {
			DBG_STRINT("Start request failed with status: ",status,1);
		}
		call MlmeStartRequestConfirm.destroy(confirm);
		
		#ifdef PROFILING
			TOSH_CLR_PROFILING5_PIN();
		#endif
	}
	
	/*******************************
	  PIB attribute handling
	 *******************************/
	
	void setRxOnWhenIdle(bool status)
	{
		Ieee_PibAttribute attribute;
		call IeeePibAttribute.create(1,&attribute);
		call IeeePibAttribute.setMacRxOnWhenIdle(attribute,status);
		
		// setAttribute is responsible for destroying the primitive.
		setAttribute(attribute);
	}
	
	void setShort(uint16_t myShort)
	{
		Ieee_PibAttribute attribute;
		call IeeePibAttribute.create(2,&attribute);
		call IeeePibAttribute.setMacShortAddress(attribute,myShort);
		
		// setAttribute is responsible for destroying the primitive.
		setAttribute(attribute);
	}
	
	void setAssoc(bool allow)
	{
		Ieee_PibAttribute attribute;
		call IeeePibAttribute.create(1,&attribute);
		call IeeePibAttribute.setMacAssociationPermit(attribute,allow);
		
		// setAttribute is responsible for destroying the primitive.
		setAttribute(attribute);
	}
	
	result_t setAttribute(Ieee_PibAttribute attribute)
	{
		Mlme_SetRequestConfirm request;
		Ieee_Status status;
		call MlmeSetRequestConfirm.create(&request);
		call MlmeSetRequestConfirm.setPibAttribute(request,attribute);
		request = call MlmeRequestConfirmSet.request(request);
		
		status = call MlmeSetRequestConfirm.getStatus(request);
		if (SUCCESS != call IeeePibAttribute.destroy(attribute)) {
			DBG_STR("Could not destroy attribute",3);
		}
		call MlmeSetRequestConfirm.destroy(request);
		if (status != IEEE802154_SUCCESS) {
			DBG_STR("Set operation was unsuccessful!",1);
			return FAIL;
		}
		return SUCCESS;
	}
	
	event void MlmeRequestConfirmSet.confirm(Mlme_SetRequestConfirm confirm)
	{
		// We don't do anything here as we handle set requests in sync.
	}
	
	/** Help function, does string compare */
	int strcmp(const char * a, const char * b)
	{
		while (*a && *b && *a == *b) { ++a; ++b; };
		return *a - *b;
	}
}